package fr.insalyon.sims.broadcast.network;

import java.io.IOException;

import javax.swing.event.EventListenerList;

/**
 * This class is a stream receiver. It has a list of DataListeners which wait for the data from the receiver.
 * All listeners get notified and execute their dataReceived method.
 * 
 * @author GONZALEZ Silvia, GOTTERO Jeremie, SATTAYAKAWEE Nutchanat
 *
 */
abstract public class BaseStreamReceiver implements IStreamReceiver {
	// ------------------------------------------------------- CONSTANTS -- //
	// ------------------------------------------------------ ATTRIBUTES -- //
	
	/**
	 * list of listeners of data the class receives
	 */
	private EventListenerList listenersList;
	
	/**
	 * thread using for receiving the data
	 */
	private Thread streamReceiverThread;
	
	// ---------------------------------------------------- CONSTRUCTORS -- //
	
	public BaseStreamReceiver() {
		// avec this. on choisit un attribut de la classe  
		this.listenersList = new EventListenerList();
		this.streamReceiverThread = null;
	}
	
	// --------------------------------------------------------- METHODS -- //
	
	/**
	 * Starts streamReceiverThread 
	 */
	public synchronized void start() {
		if (this.streamReceiverThread == null) {
			// Create receiver thread
			this.streamReceiverThread = new Thread() {
				public void run() {
					try {
						// Initialize resources (sockets...)
						BaseStreamReceiver.this.init();
						
						// Read data on stream
						while (!isInterrupted())
							readStream();
						
						// Clean up resources (sockets...)
						cleanUp();
					} catch (IOException e) {
						fireStreamError(e);
					}
				}
			};
			this.streamReceiverThread.setName(getClass().getSimpleName() + "Thread");
			this.streamReceiverThread.start();
		}
	}
	
	/**
	 * Stops streamReceiverThread
	 */
	public synchronized void stop() {
		if (this.streamReceiverThread != null) {
			// Interrupt receiver thread
			this.streamReceiverThread.interrupt();
			this.streamReceiverThread = null;
		}
	}
	
	/**
	 * Adds DataListener to listenersList
	 * 
	 * @param listener listener to be added
	 */
	public void addDataListener(DataListener listener) {
		listenersList.add(DataListener.class, listener);
	}
	
	/**
	 * Removes DataListener from listenersList. This method does nothing if the listener doesn't exist. 
	 * 
	 * @param listener listener to be removed.
	 */
	public void removeDataListener(DataListener listener) {
		listenersList.remove(DataListener.class, listener);
	}
	
	/**
	 * Adds DataErrorListener to listenersList. 
	 * This listener will be used when there is an error while receiving the stream.
	 * 
	 * @param listener listener to be added
	 */
	public void addDataErrorListener(DataErrorListener listener) {
		listenersList.add(DataErrorListener.class, listener);
	}
	
	/**
	 * Removes DataErrorListener from listenersList. This method does nothing if the listener doesn't exist. 
	 * 
	 * @param listener listener to be removed.
	 */
	public void removeDataErrorListener(DataErrorListener listener) {
		listenersList.remove(DataErrorListener.class, listener);
	}
	
	// ------------------------------------------------- PRIVATE METHODS -- //
	
	/**
	 * Initializes the running streamReceiverThread
	 */
	abstract protected void init() throws IOException;
	
	/**
	 * Reads stream
	 * @throws IOException
	 */
	abstract protected void readStream() throws IOException;
	
	/**
	 * Cleans all resources used in streamReceiverThread
	 * @throws IOException
	 */
	abstract protected void cleanUp() throws IOException;
	
	/**
	 * Sends the data to all DataListeners
	 * @param data data received
	 * @param offset data offset
	 * @param length data length
	 */
	protected void fireDataReceived(byte[] data, int offset, int length) {
		Object[] listeners = listenersList.getListenerList();
		
		for (int i = listeners.length - 2; i >= 0; i -= 2)
			if (listeners[i] == DataListener.class)
				((DataListener) listeners[i + 1]).dataReceived(
						new DataEvent(this, data, offset, length));
	}
	
	/**
	 * Sends error to all DataErrorListeners
	 * @param e error
	 */
	protected void fireStreamError(Exception e) {
		Object[] listeners = listenersList.getListenerList();
		
		for (int i = listeners.length - 2; i >= 0; i -= 2)
			if (listeners[i] == DataErrorListener.class)
				((DataErrorListener) listeners[i + 1]).streamError(
						new DataErrorEvent(this, e));
	}
	
	// --------------------------------------------------- INNER CLASSES -- //
}
